﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.Threading.Tasks;
using System.ComponentModel;
using CommandLine;
using CommandLine.Text;

namespace PortScan
{
    class Program
    {

        class Options : CommandLineOptionsBase
        {
            [Option("a", "address", Required = true, HelpText = "DNS Name or IP Address to scan")]
            public string address { get; set; }
            
            [Option("s", "start", Required = true, HelpText = "Port number to check or first number of a range (assuming end is provided)")]
            public int start { get; set; }
            
            [Option("e", "end", Required = false, HelpText = "(optional) end of the range")]
            public int end { get; set; }

            [HelpOption]
            public string GetUsage()
            {
                return HelpText.AutoBuild(this, (HelpText current) => HelpText.DefaultParsingErrorsHandler(this, current));
            }
        }

        static void Main(string[] args)
        {
            var options = new Options();
            ICommandLineParser parser = new CommandLineParser();
            try
            {
                if (parser.ParseArguments(args, options))
                {
                    // GO ON 
                    // Everything has parsed fine let's go ! 

                    //Get server info
                    Server srv = new Server(options.address);
                    //Set end = start if no end provided
                    if (options.end == 0) { options.end = options.start; }

                    //start time
                    long start = System.Environment.TickCount;
                    //handle the waiting task and enable cancellation of the task so we can end it when all the treads have completed
                    Console.Write("Waiting ...");
                    var waitTokenSource = new CancellationTokenSource();
                    var waitToken = waitTokenSource.Token;
                    Task.Factory.StartNew(() => Waiting(waitToken), waitToken);

                    //delegate function to safely encapsulate thread information
                    Func<object, PortInfo> runthreads = (object obj) =>
                    {
                        int i = (int)obj;
                        return PortCheck.Check(srv.ipv4addrs[0], i);
                    };

                    //create the array and start the tasks
                    Task<PortInfo>[] taskArray = new Task<PortInfo>[options.end - options.start + 1];
                    for (int i = options.start; i <= options.end; i++)
                    {
                        taskArray[i - options.start] = Task<PortInfo>.Factory.StartNew(runthreads, i);
                    }

                    //wait on all the tasks to complete
                    try
                    {
                        Task.WaitAll(taskArray); //wait that all the threads running portcheck are finished         
                    }
                    catch (AggregateException e)
                    {
                        for (int j = 0; j < e.InnerExceptions.Count; j++)
                        {
                            Console.WriteLine("\n-------------------------------------------------\n{0}", e.InnerExceptions[j].ToString());
                        }
                    }

                    //cancel the waiting task
                    waitTokenSource.Cancel();

                    //var tasks = from t in taskArray orderby t.Result.number select t; //not necessary as tasks arlready ordered correctly

                    //stop time
                    long end = System.Environment.TickCount;
                    
                    //cr lf so result do not show directly after waiting dots
                    Console.Write("\r\n");
                    
                    //process the results
                    foreach (Task<PortInfo> task in taskArray)
                    {
                        if (task.Result.open) { Console.WriteLine(task.Result.ipaddr + " : " + task.Result.number + " is open "); }
                        else { Console.WriteLine(task.Result.ipaddr + " : " + task.Result.number + " is closed "); };
                    }

                    Console.WriteLine("\nFinished"); //once done
                    //time elapsed
                    Console.WriteLine("{0} second(s), {1} ticks to check {2} port(s)", (end - start) / 1000, end - start, options.end - options.start + 1);
                    //Wait before close console
                    //Console.ReadLine();
                    return;
                }
                else
                {
                    Console.WriteLine(options.GetUsage());
                    return;
                }
            }
            catch (Exception e)
            {
                Console.WriteLine(e.Message);
                return;
            }
        }
        

        static void Waiting(CancellationToken ct)
        {
            while (! ct.IsCancellationRequested)
            {
                Console.Write(".");
                Thread.Sleep(1000);
            }
            
            //Console.WriteLine("Cancellation requested");
        }

        

        /*
        static void Main(string[] args)
        {
            string name = (args.Length < 1) ? Dns.GetHostName() : args[0];
            Server srv = new Server("localhost");
            int lastport = 0;
            Console.Write("Number of ports to check : ");
            //number of ports to check 
            try{
                lastport = Convert.ToInt32(Console.ReadLine());
            }catch(Exception e){
                Console.WriteLine("Error reading integer : " + e.Message);
                Environment.Exit(0);
            }

            //start time
            long start = System.Environment.TickCount;

            //handle the waiting task and enable cancellation of the task so we can end it when all the treads have completed
            Console.Write("Waiting ...");
            var waitTokenSource = new CancellationTokenSource();
            var waitToken = waitTokenSource.Token;
            Task.Factory.StartNew(() =>  Waiting(waitToken), waitToken);
     
            //delegate function to safely encapsulate thread information
            Func<object, PortInfo> runthreads = (object obj) =>
            {
                int i = (int)obj;
                return PortCheck.Check(srv.ipv4addrs[0], i);
            };

            //create the array and start the tasks
            Task<PortInfo>[] taskArray = new Task<PortInfo>[lastport];
            for (int i = 0; i < taskArray.Length; i++)
            {
                taskArray[i] = Task<PortInfo>.Factory.StartNew(runthreads, i);
            }

            //wait on all the tasks to complete
            try
            {
                Task.WaitAll(taskArray); //wait that all the threads running portcheck are finished         
            }
            catch (AggregateException e)
            {
                for (int j = 0; j < e.InnerExceptions.Count; j++)
                {
                    Console.WriteLine("\n-------------------------------------------------\n{0}", e.InnerExceptions[j].ToString());
                }
            }

            //cancel the waiting task
            waitTokenSource.Cancel();

            //var tasks = from t in taskArray orderby t.Result.number select t; //not necessary as tasks arlready ordered correctly
           
            //stop time
            long end = System.Environment.TickCount;

            //process the results
            foreach (Task<PortInfo> task in taskArray)
            {
                if (task.Result.open)   { Console.WriteLine(task.Result.ipaddr + " : " + task.Result.number + " is open "); }
                else                    { Console.WriteLine(task.Result.ipaddr + " : " + task.Result.number + " is closed "); };
            }

            Console.WriteLine("\nFinished"); //once done
            //time elapsed
            Console.WriteLine("{0} second(s), {1} ticks to check {2} port(s)", (end - start) / 1000, end - start, lastport);
            //Wait before close console
            Console.ReadLine();
        }
         * */

    }
}
